﻿using hyjx.comm.Exceptions;
using hyjx.comm.Interfaces;
using S7.Net;
using S7.Net.Types;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using Boolean = System.Boolean;

namespace hyjx.comm
{
    /// <summary>
    /// S7 协议
    /// </summary>
    public class S7Protocol : IS7Protocol
    {
        private int inConnect;
        readonly CpuType _cpu;
        readonly string _ip;
        Plc _plc;

        /// <summary>
        /// 创建一个新的 S7 协议
        /// </summary>
        /// <param name="cpu">CPU 类型</param>
        /// <param name="ip">IP 地址</param>
        /// <param name="rack">机架位</param>
        /// <param name="slot">插槽位</param>
        public S7Protocol(CpuType cpu, string ip, short rack = 0, short slot = 0)
        {
            _cpu = cpu;
            _ip = ip;
            _plc = new Plc(_cpu, ip, rack, slot);
        }

        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool IsConnected => _plc != null ? _plc.IsConnected : throw new S7ProtocolException("PlcIsNull");
        /// <summary>
        /// IP
        /// </summary>
        public string Ip => _ip;
        /// <summary>
        /// Cpu 类型
        /// </summary>
        public CpuType Cpu => _cpu;

        /// <summary>
        /// 连接
        /// </summary>
        /// <exception cref="S7ProtocolException">Plc 尚未初始化</exception>
        public void Connect()
        {
            if (_plc == null)
                throw new S7ProtocolConnectException("PlcIsNull");

            if (Interlocked.Exchange(ref inConnect, 1) == 0)
            {
                try
                {
                    _plc.Open();
                }
                catch (PlcException ex)
                {
                    throw new S7ProtocolConnectException(nameof(PlcException), ex);
                }
                finally
                {
                    Interlocked.Exchange(ref inConnect, 0);
                }
            }
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        /// <exception cref="S7ProtocolDisconnectException">Plc 尚未初始化</exception>
        public void Disconnect()
        {
            if (_plc == null)
                throw new S7ProtocolDisconnectException("PlcIsNull");

            try
            {
                _plc.Close();
            }
            catch (PlcException ex)
            {
                throw new S7ProtocolDisconnectException(nameof(PlcException), ex);
            }
        }
        /// <summary>
        /// 读取整型值
        /// </summary>
        /// <param name="address">地址</param>
        /// <returns>整型值</returns>
        /// <exception cref="S7ProtocolReadIntException">读取整型值错误</exception>
        public int ReadInt(string address)
        {
            try
            {
                object result = Read(address);
                if (address.ToLower().Contains("dbw")) // 如果是 Int
                    return result.ToShort();
                else
                    return 0;
            }
            catch (S7ProtocolReadException ex)
            {
                throw new S7ProtocolReadIntException(nameof(S7ProtocolReadException), ex);
            }
            catch (DataConvertorToBoolException ex)
            {
                throw new S7ProtocolReadIntException(nameof(DataConvertorException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadIntException(nameof(Exception), ex);
            }
        }
        /// <summary>
        /// 读取浮点值
        /// </summary>
        /// <param name="address">地址</param>
        /// <returns>浮点值</returns>
        /// <exception cref="S7ProtocolReadFloatException">读取浮点值错误</exception>
        public float ReadFloat(string address)
        {
            try
            {
                object result = Read(address);
                if (address.ToLower().Contains("dbd")) // 如果是 real
                    return result.ToFloat();
                else
                    return 0;
            }
            catch (S7ProtocolReadException ex)
            {
                throw new S7ProtocolReadFloatException(nameof(S7ProtocolReadException), ex);
            }
            catch (DataConvertorToBoolException ex)
            {
                throw new S7ProtocolReadFloatException(nameof(DataConvertorException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadFloatException(nameof(Exception), ex);
            }
        }
        /// <summary>
        /// 读取布尔值
        /// </summary>
        /// <param name="address">地址</param>
        /// <returns>布尔值</returns>
        /// <exception cref="S7ProtocolReadBoolException">读取布尔值错误</exception>
        public bool ReadBool(string address)
        {
            try
            {
                object result = Read(address);
                if (address.ToLower().Contains("dbx")) // 如果是 bool
                    return result.ToBool();
                else
                    return false;
            }
            catch (S7ProtocolReadException ex)
            {
                throw new S7ProtocolReadBoolException(nameof(S7ProtocolReadException), ex);
            }
            catch (DataConvertorToBoolException ex)
            {
                throw new S7ProtocolReadBoolException(nameof(DataConvertorException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadBoolException(nameof(Exception), ex);
            }
        }
        /// <summary>
        /// 读取字节流
        /// </summary>
        /// <param name="db">数据块</param>
        /// <param name="startByteAdrress">起始地址</param>
        /// <param name="length">读取长度</param>
        /// <returns>字节流</returns>
        /// <exception cref="S7ProtocolReadBytesException">读取字节流错误</exception>
        public byte[] ReadBytes(int db, int startByteAdrress, int length)
        {
            if (_plc == null)
                throw new S7ProtocolReadBytesException("PlcIsNull");

            try
            {
                if (!IsConnected)
                    _plc.Open();

                return _plc.ReadBytes(DataType.DataBlock, db, startByteAdrress, length);
            }
            catch (PlcException ex)
            {
                throw new S7ProtocolReadBytesException(nameof(PlcException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadBytesException(nameof(Exception), ex);
            }
        }

        /// <summary>
        /// 读取字符串
        /// </summary>
        /// <param name="db">数据块号</param>
        /// <param name="startByteAddress">起始寄存器地址</param>
        /// <param name="length">字节长度（依据 S7 规则，字符串的长度是 n + 2）</param>
        /// <exception cref="S7ProtocolReadStringException">读取字符串异常</exception>
        public string ReadString(int db, int startByteAddress, int length)
        {
            try
            {
                byte[] bytes = ReadBytes(db, startByteAddress, length);
                byte stringByteLength = bytes[0]; // 总字符数
                byte actualStringByteLength = bytes[1]; // 当前字符数
                byte[] nameBytes = new byte[actualStringByteLength];
                Array.Copy(bytes, 2, nameBytes, 0, actualStringByteLength);
                string result = nameBytes.FromBytesToString(Encoding.UTF8);
                return result;
            }
            catch (S7ProtocolReadBytesException ex)
            {
                throw new S7ProtocolReadStringException(nameof(S7ProtocolReadBytesException), ex);
            }
            catch (DataConvertorToStringException ex)
            {
                throw new S7ProtocolReadStringException(nameof(DataConvertorToStringException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadStringException(nameof(Exception), ex);
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="address">寄存器地址</param>
        /// <returns></returns>
        /// <exception cref="S7ProtocolReadException">读取错误，可能是 PLC 尚未初始化</exception>
        object Read(string address)
        {
            if (_plc == null)
                throw new S7ProtocolReadException("PlcIsNull");

            try
            {
                if (!IsConnected)
                    _plc.Open();

                return _plc.Read(address);
            }
            catch (PlcException ex)
            {
                throw new S7ProtocolReadException(nameof(PlcException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolReadException(nameof(Exception), ex);
            }
        }


        /// <summary>
        /// 写值
        /// </summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="address">写入地址</param>
        /// <param name="value">值</param>
        /// <exception cref="S7ProtocolWriteException">写值错误</exception>
        public void Write<T>(string address, T value) where T : new()
        {
            if (value == null || string.IsNullOrEmpty(address))
                throw new S7ProtocolWriteException(nameof(ArgumentNullException));

            if (_plc == null)
                throw new S7ProtocolWriteException("PlcIsNull");

            try
            {
                if (!IsConnected)
                    _plc.Open();

                if (typeof(T) == typeof(Int32) || typeof(T) == typeof(Int16))
                    _plc.Write(address, Convert.ToInt16(value));
                else if (typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(Decimal))
                    _plc.Write(address, Convert.ToSingle(value));
                else
                    _plc.Write(address, value);
            }
            catch (PlcException ex)
            {
                throw new S7ProtocolWriteException(nameof(PlcException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolWriteException(nameof(Exception), ex);
            }
        }
        /// <summary>
        /// 写值
        /// </summary>
        /// <param name="db">数据块</param>
        /// <param name="startByteAdrress">起始地址</param>
        /// <param name="bytes">字节流</param>
        /// <exception cref="S7ProtocolWriteException">写值错误，可能是 PLC 尚未初始化</exception>
        public void Write(int db, int startByteAdrress, byte[] bytes)
        {
            if (_plc == null)
                throw new S7ProtocolWriteException("PlcIsNull");

            try
            {
                if (!IsConnected)
                    _plc.Open();

                _plc.WriteBytes(DataType.DataBlock, db, startByteAdrress, bytes);
            }
            catch (PlcException ex)
            {
                throw new S7ProtocolWriteException(nameof(PlcException), ex);
            }
            catch (Exception ex)
            {
                throw new S7ProtocolWriteException(nameof(Exception), ex);
            }
        }


        /// <summary>
        /// 写值
        /// </summary>
        /// <param name="db">数据块号</param>
        /// <param name="address">地址</param>
        /// <param name="value">写入的值</param>
        public void Write(int db, int address, short value)
        {
            Write($"DB{db}.DBW{address}", value);
        }
        /// <summary>
        /// 写值
        /// </summary>
        /// <param name="db">数据块号</param>
        /// <param name="address">地址</param>
        /// <param name="value">写入的值</param>
        public void Write(int db, int address, float value)
        {
            Write($"DB{db}.DBD{address}", value);
        }
        /// <summary>
        /// 写值
        /// </summary>
        /// <param name="db">数据块号</param>
        /// <param name="address">地址</param>
        /// <param name="value">写入的值</param>
        public void Write(int db, float address, bool value)
        {
            Write($"DB{db}.DBX{address:F1}", value);
        }
        /// <summary>
        /// 写值
        /// </summary>
        /// <param name="db">数据块号</param>
        /// <param name="address">地址</param>
        /// <param name="addressLength">地址长度</param>
        /// <param name="text">写入的文本</param>
        public void Write(int db, int address, byte addressLength, string text)
        {
            var temp = Encoding.ASCII.GetBytes(text);
            List<byte> bytes = new List<byte>() { addressLength, (byte)temp.Length };
            bytes.AddRange(temp);
            Write(db, address, bytes.ToArray());
        }


        #region[  释放对象  ]

        /// <summary>
        /// 释放对象
        /// </summary>
        public void Dispose()
        {
            Dispose(disposing: true);
            // 释放完成后，调用该方法通知 GC 将该对象从终结器队列中移除。这样该对象的析构函数就不会被执行。
            GC.SuppressFinalize(this);
        }

        private bool disposed = false;

        /// <summary>
        /// 执行具体的释放
        /// </summary>
        /// <param name="disposing">false : 由终结器释放</param>
        protected virtual void Dispose(bool disposing)
        {
            // 如果以执行完释放，不需要做任何动作
            if (this.disposed)
            {
                return;
            }

            if (disposing)
            {
                // 在此释放托管资源
                _plc.Close();
                _plc = null;
            }

            // 开始释放非托管资源

            // 释放已完成
            disposed = true;
        }

        /// <summary>
        /// 析构函数（终结器）
        /// </summary>
        ~S7Protocol()
        {
            // 不要在此处编写清理代码。
            // 应调用 Dispose(disposing: false)
            Dispose(disposing: false);
        }
        #endregion

    }
}
